package com.gonsin.ta.logic.node.groovy;

import com.gonsin.ta.logic.annotations.NodeComponent;
import com.gonsin.ta.logic.components.RunNode;
import com.gonsin.ta.logic.components.SignalNode;
import com.gonsin.ta.logic.components.SwitchNode;
import com.gonsin.ta.logic.enums.NodeTypeEnum;
import com.gonsin.ta.logic.exceptions.TaException;
import com.gonsin.ta.logic.node.*;
import com.gonsin.ta.logic.parse.condition.context.ChainContextImpl;
import com.gonsin.ta.logic.parse.condition.context.ChainContextGroovyProxy;
import com.gonsin.ta.logic.spring.ComponentScanner;
import com.gonsin.ta.logic.spring.NodeProxyUtil;
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

@Slf4j
@Component
public class GroovyScriptExecutor implements InitializingBean {

    private volatile boolean inited = false;

//    private ScriptEngine scriptEngine;


    // 用于直接执行groovy脚本
    private GroovyShell groovyShell;

    // 用于加载groovy重写的类
    private GroovyClassLoader groovyClassLoader;

    // 用于执行时注入bean对象
    @Autowired
    private ApplicationContext applicationContext;

    private ChainContextGroovyProxy contextProxy;

    private ScriptEngineManager factory = null;
    private ScriptEngine engine = null;
    private Compilable compilable = null;


    public void init(){
        if(inited){
            return;
        }

        groovyClassLoader = new GroovyClassLoader(GroovyScriptExecutor.class.getClassLoader());


        Binding bindings = new Binding();
        ComponentScanner.allComponents.forEach(bindings::setProperty);
        contextProxy = new ChainContextGroovyProxy(applicationContext);
        bindings.setProperty("context", contextProxy);
        groovyShell = new GroovyShell(GroovyScriptExecutor.class.getClassLoader(), bindings);
        inited = true;
    }

    /**
     * 加载脚本
     */
//    public CompiledScript load(String groovyScript){
//        try {
//            return ((Compilable) scriptEngine).compile(groovyScript);
//        } catch (ScriptException e) {
//            log.error(e.toString(), e);
//            throw new TaException("groovy编译错误", e);
//        }
//    }

    /**
     * 执行脚本
     */
    @SneakyThrows
    public Object execute(Object instance, ChainContextImpl context){
        contextProxy.setContextLocal(context);
        if(instance instanceof String){
            String script = (String) instance;
            Object exec = groovyShell.evaluate(script);
            return exec;
        }

        if(instance instanceof Script){
            return ((Script) instance).run();
        }

        if(instance instanceof NodeComponentProxy){
            ((NodeComponentProxy) instance).execute(contextProxy);
        }
        return null;
//        throw new TaException("执行节点【{}】出错脚本出错");
    }


    public Object parseClass(GroovyNodeAdapter node, Class clazz){
        if(RunNode.class.isAssignableFrom(clazz)){
            String nodeId = node.getNodeId();
            NodeTypeEnum nodeType = node.getNodeType();
            NodeComponent component = (NodeComponent) clazz.getDeclaredAnnotation(NodeComponent.class);
            if(component != null){
                nodeId = nodeId == null ? component.nodeId() : null;
                nodeType = nodeType == null ? NodeTypeEnum.RUN : null;
            }
            if(nodeId == null){
                throw new TaException("groovy解析出错，请添加@NodeComponent，并指定nodeId值，或者在<node>标签内添加id值，文件位置【{}】", node.getFilename());
            }
            node.setNodeType(nodeType);
            node.setNodeId(nodeId);
            NodeComponentProxy nodeComponent = NodeProxyUtil.bean2NodeComponent(nodeId, nodeType, clazz);
            return nodeComponent;
        }

        if(SwitchNode.class.isAssignableFrom(clazz)){
            String nodeId = node.getNodeId();
            NodeTypeEnum nodeType = node.getNodeType();
            NodeComponent component = (NodeComponent) clazz.getDeclaredAnnotation(NodeComponent.class);
            if(component != null){
                nodeId = nodeId == null ? component.nodeId() : null;
                nodeType = nodeType == null ? NodeTypeEnum.SWITCH : null;
            }
            if(nodeId == null){
                throw new TaException("groovy解析出错，请添加@NodeComponent，并指定nodeId值，或者在<node>标签内添加id值，文件位置【{}】", node.getFilename());
            }
            node.setNodeType(nodeType);
            node.setNodeId(nodeId);
            NodeComponentProxy nodeComponent = NodeProxyUtil.bean2NodeComponent(nodeId, nodeType, clazz);
            return nodeComponent;
        }

        if(SignalNode.class.isAssignableFrom(clazz)){
            String nodeId = node.getNodeId();
            NodeTypeEnum nodeType = node.getNodeType();
            NodeComponent component = (NodeComponent) clazz.getDeclaredAnnotation(NodeComponent.class);
            if(component != null){
                nodeId = nodeId == null ? component.nodeId() : null;
                nodeType = nodeType == null ? NodeTypeEnum.SIGNAL : null;
            }
            if(nodeId == null){
                throw new TaException("groovy解析出错，请添加@NodeComponent，并指定nodeId值，或者在<node>标签内添加id值，文件位置【{}】", node.getFilename());
            }
            node.setNodeType(nodeType);
            node.setNodeId(nodeId);
            NodeComponentProxy nodeComponent = NodeProxyUtil.bean2NodeComponent(nodeId, nodeType, clazz);
            return nodeComponent;
        }

        return null;
    }


    /**
     * 解析groovy脚本
     */
    @SneakyThrows
    public Object parse(GroovyNodeAdapter node, String groovyScript) {
        Object instance = null;
        try {
            Class clazz = groovyClassLoader.parseClass(groovyScript);
            if(clazz != null){
                instance = parseClass(node, clazz);
                if(instance != null){
                    return instance;
                }
            }
        } catch (TaException ex) {
            throw ex;
        } catch (Exception ex) {

        }


        // 语法校验
//        CompiledScript script =
//        compilable.compile(groovyScript);
//        Script script =
        return groovyShell.parse(groovyScript);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }

    /**
     * 临时解析groovy，为了获取类名
     * @param groovyScript
     */
    public Class parseClass(String nodeId, NodeTypeEnum nodeType, String groovyScript) {
        try {
            Class clazz = groovyClassLoader.parseClass(groovyScript);
            if(RunNode.class.isAssignableFrom(clazz)){
                NodeComponent component = (NodeComponent) clazz.getDeclaredAnnotation(NodeComponent.class);
                if(component != null){
                    if(!StringUtils.equals(nodeId, component.nodeId())){
                        throw new TaException("groovy解析出错，@NodeComponent中的节点nodeId和实际nodeId不对应");
                    }
                    if(nodeType != NodeTypeEnum.RUN){
                        throw new TaException("groovy解析出错，节点类型不对应");
                    }
                }
            }

            if(SwitchNode.class.isAssignableFrom(clazz)){
                NodeComponent component = (NodeComponent) clazz.getDeclaredAnnotation(NodeComponent.class);
                if(component != null){
                    if(!StringUtils.equals(nodeId, component.nodeId())){
                        throw new TaException("groovy解析出错，@NodeComponent中的节点nodeId和实际nodeId不对应");
                    }
                    if(nodeType != NodeTypeEnum.SWITCH){
                        throw new TaException("groovy解析出错，节点类型不对应");
                    }
                }
            }

            if(SignalNode.class.isAssignableFrom(clazz)){
                NodeComponent component = (NodeComponent) clazz.getDeclaredAnnotation(NodeComponent.class);
                if(component != null){
                    if(!StringUtils.equals(nodeId, component.nodeId())){
                        throw new TaException("groovy解析出错，@NodeComponent中的节点nodeId和实际nodeId不对应");
                    }
                    if(nodeType != NodeTypeEnum.SIGNAL){
                        throw new TaException("groovy解析出错，节点类型不对应");
                    }
                }
            }
            return clazz;
        } catch (Exception ex) {
            return null;
        }
    }
}
